Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle errors happen during streaming components #1648

Merged

Conversation

AbanoubGhadban
Copy link
Collaborator

@AbanoubGhadban AbanoubGhadban commented Aug 26, 2024

Summary

Remove this paragraph and provide a general description of the code changes in your pull
request... were there any bugs you had fixed? If so, mention them. If
these bugs have open GitHub issues, be sure to tag them here as well,
to keep the conversation linked together.

Pull Request checklist

Remove this line after checking all the items here. If the item is not applicable to the PR, both check it out and wrap it by ~.

  • Add/update test to cover these changes
  • Update documentation
  • Update CHANGELOG file
    Add the CHANGELOG entry at the top of the file.

Other Information

Remove this paragraph and mention any other important and relevant information such as benchmarks.


This change is Reviewable

Summary by CodeRabbit

  • New Features

    • Enhanced error handling for server-rendered React components, improving feedback during streaming.
    • Introduced new methods for better management of rendering errors and configuration retrieval.
    • Added a new property to indicate shell readiness in the rendering results.
    • Added support for replaying console logs during server rendering for better debugging.
    • Introduced a new helper method for integrating streamed components into views.
  • Bug Fixes

    • Improved robustness of error handling during the rendering process, preventing disruptions from streaming errors.
    • Corrected the type of the ReactOnRails.registerStore method, renaming it to registerStoreGenerators for clarity.
  • Tests

    • Added a suite of tests for the streamServerRenderedReactComponent function to validate streaming behavior and error handling.
  • Chores

    • Upgraded React and its type definitions to a specific canary version for improved performance and compatibility.

@AbanoubGhadban AbanoubGhadban changed the base branch from master to abanoubghadban/pro426-add-support-for-console-replay-while-streaming-component August 26, 2024 14:32
Copy link
Contributor

coderabbitai bot commented Aug 26, 2024

Walkthrough

The changes introduce enhancements to the ReactOnRails library, focusing on error handling and control flow for rendering React components. Key additions include the raise_prerender_error and should_raise_streaming_prerender_error? methods in the helper module, as well as new methods in the RenderOptions class to manage server rendering errors. The serverRenderReactComponent.ts file has been updated to improve streaming logic and error management, ensuring a more robust server-side rendering process.

Changes

File Change Summary
lib/react_on_rails/helper.rb Added raise_prerender_error and should_raise_streaming_prerender_error? methods for improved error handling.
lib/react_on_rails/react_component/render_options.rb Added raise_non_shell_server_rendering_errors and retrieve_react_on_rails_pro_config_value_for methods.
node_package/src/serverRenderReactComponent.ts Updated stream handling logic and enhanced error management in streamServerRenderedReactComponent.
node_package/src/types/index.ts Updated RenderResult interface to include optional isShellReady property.
node_package/tests/streamServerRenderedReactComponent.test.jsx Introduced tests for streamServerRenderedReactComponent to validate streaming behavior and error handling.
package.json Updated react and react-dom dependencies to specific canary versions.

Possibly related PRs

Suggested reviewers

  • Justin808
  • Alexeyr-ci

🐇 In the garden, hopping about,
New features sprout, without a doubt.
Streams of React, flowing with grace,
Errors handled, in their place.
With tests now set, all is bright,
A joyful code, in morning light! 🌼


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Inline review comments failed to post

Actionable comments posted: 1

Outside diff range, codebase verification and nitpick comments (2)
lib/react_on_rails/helper.rb (2)

539-543: Remove trailing whitespace.

The new method should_raise_streaming_prerender_error is correctly implemented and integrates well with the existing structure. However, there is trailing whitespace on lines 540, 541, and 605.

Apply this diff to remove the trailing whitespace:

-      chunk_json_result["hasErrors"] && 
+      chunk_json_result["hasErrors"] &&
-        ((render_options.raise_on_prerender_error && !chunk_json_result["isShellReady"]) || 
+        ((render_options.raise_on_prerender_error && !chunk_json_result["isShellReady"]) ||
-      
+
Tools
rubocop

[convention] 540-540: Trailing whitespace detected.

(Layout/TrailingWhitespace)


[convention] 541-541: Trailing whitespace detected.

(Layout/TrailingWhitespace)


601-603: Refactor nested if statement.

Convert if nested inside else to elsif for better readability.

Apply this diff to refactor the nested if statement:

-      else
-        if result["hasErrors"] && render_options.raise_on_prerender_error
+      elsif result["hasErrors"] && render_options.raise_on_prerender_error
Tools
rubocop

[convention] 601-601: Convert if nested inside else to elsif.

(Style/IfInsideElse)

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between bb9a8a2 and 4829dcd.

Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
Files selected for processing (16)
  • .gitignore (1 hunks)
  • jest.config.js (1 hunks)
  • lib/react_on_rails/helper.rb (6 hunks)
  • lib/react_on_rails/react_component/render_options.rb (3 hunks)
  • lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb (4 hunks)
  • node_package/src/ReactOnRails.ts (2 hunks)
  • node_package/src/buildConsoleReplay.ts (2 hunks)
  • node_package/src/serverRenderReactComponent.ts (2 hunks)
  • node_package/src/types/index.ts (2 hunks)
  • node_package/tests/ReactOnRails.test.js (1 hunks)
  • node_package/tests/jest.setup.js (1 hunks)
  • package.json (2 hunks)
  • spec/dummy/config/webpack/alias.js (1 hunks)
  • spec/dummy/config/webpack/commonWebpackConfig.js (1 hunks)
  • spec/dummy/config/webpack/webpackConfig.js (1 hunks)
  • spec/dummy/spec/helpers/react_on_rails_helper_spec.rb (2 hunks)
Files skipped from review due to trivial changes (1)
  • .gitignore
Additional context used
Biome
node_package/src/buildConsoleReplay.ts

[error] 17-17: Use Array.isArray() instead of instanceof Array.

instanceof Array returns false for array-like objects and arrays from other execution contexts.
Unsafe fix: Use Array.isArray() instead.

(lint/suspicious/useIsArray)

rubocop
lib/react_on_rails/react_component/render_options.rb

[convention] 134-134: Add empty line after guard clause.

(Layout/EmptyLineAfterGuardClause)

lib/react_on_rails/helper.rb

[convention] 540-540: Trailing whitespace detected.

(Layout/TrailingWhitespace)


[convention] 541-541: Trailing whitespace detected.

(Layout/TrailingWhitespace)


[convention] 601-601: Convert if nested inside else to elsif.

(Style/IfInsideElse)


[convention] 605-605: Trailing whitespace detected.

(Layout/TrailingWhitespace)

Additional comments not posted (30)
jest.config.js (1)

4-4: LGTM!

The addition of the setupFiles array is correctly implemented and enhances the testing environment by allowing pre-test setup tasks.

The code changes are approved.

spec/dummy/config/webpack/alias.js (1)

7-7: LGTM!

The addition of the alias mapping for stream to stream-browserify is correctly implemented and enhances compatibility with browser environments.

The code changes are approved.

node_package/tests/jest.setup.js (1)

1-13: LGTM!

The polyfill setup for TextEncoder and TextDecoder is correctly implemented and ensures compatibility with the jsdom environment.

The code changes are approved.

spec/dummy/config/webpack/webpackConfig.js (1)

7-7: LGTM!

The addition of the fallback for stream to use stream-browserify is correctly implemented to ensure compatibility with certain libraries that expect Node.js-style streams.

The code changes are approved.

node_package/src/buildConsoleReplay.ts (1)

42-43: LGTM!

The function is correctly implemented to accept the new parameter and pass it to consoleReplay.

The code changes are approved.

spec/dummy/config/webpack/commonWebpackConfig.js (1)

44-44: LGTM!

The addition of process: 'process/browser' to the ProvidePlugin is correctly implemented to ensure compatibility with certain libraries that expect the process object to be defined.

The code changes are approved.

package.json (4)

19-19: Verify compatibility with ReactDOM 18.

The upgrade to @types/react-dom version ^18.2.0 may introduce breaking changes and new features. Ensure that the codebase is compatible with ReactDOM 18.

Run the following script to verify the usage of ReactDOM 18 features:


18-18: Verify compatibility with React 18.

The upgrade to @types/react version ^18.2.0 may introduce breaking changes and new features. Ensure that the codebase is compatible with React 18.

Run the following script to verify the usage of React 18 features:


42-42: Verify compatibility with React 18.

The upgrade to react version ^18.2.0 may introduce breaking changes and new features. Ensure that the codebase is compatible with React 18.

Run the following script to verify the usage of React 18 features:

Verification successful

React 18 Compatibility Verified

The codebase does not appear to use any React 18 specific features or deprecated lifecycle methods that could cause compatibility issues. The upgrade to react version ^18.2.0 is likely safe.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of React 18 features.

# Test: Search for React 18 specific features. Expect: No breaking changes.
rg --type js --type ts 'ReactDOM.createRoot'

Length of output: 44


Script:

#!/bin/bash
# Description: Verify compatibility with React 18 by searching for other React 18 features and changes.

# Search for the use of other React 18 specific features.
rg --type js --type ts 'useTransition|useDeferredValue|SuspenseList|startTransition'

# Check for deprecated lifecycle methods that might have been affected by the upgrade.
rg --type js --type ts 'componentWillMount|componentWillReceiveProps|componentWillUpdate'

Length of output: 178


43-43: Verify compatibility with ReactDOM 18.

The upgrade to react-dom version ^18.2.0 may introduce breaking changes and new features. Ensure that the codebase is compatible with ReactDOM 18.

Run the following script to verify the usage of ReactDOM 18 features:

Verification successful

ReactDOM 18 Compatibility Verified with Caution

The codebase uses ReactDOM.hydrateRoot, a React 18 feature, indicating compatibility with React 18. However, ensure that its implementation is correct and does not introduce any issues.

  • node_package/src/clientStartup.ts: Usage of ReactDOM.hydrateRoot.
Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the usage of ReactDOM 18 features.

# Test: Search for ReactDOM 18 specific features. Expect: No breaking changes.
rg --type js --type ts 'ReactDOM.createRoot'

Length of output: 44


Script:

#!/bin/bash
# Description: Verify the usage of other ReactDOM 18 features and potential breaking changes.

# Test: Search for other ReactDOM 18 specific features and changes.
rg --type js --type ts 'ReactDOM.unstable_' # Check for any usage of unstable features.
rg --type js --type ts 'ReactDOM.flushSync' # Check for usage of flushSync, a new API in React 18.
rg --type js --type ts 'ReactDOM.hydrateRoot' # Check for usage of hydrateRoot, another new API in React 18.

Length of output: 264

lib/react_on_rails/react_component/render_options.rb (3)

90-92: Verify configuration value for non-shell server rendering errors.

The method retrieves a configuration value for non-shell server rendering errors. Ensure that the configuration value is correctly set in the configuration files.

Run the following script to verify the configuration value:


110-112: Verify streaming option.

The method checks the streaming option from the options hash. Ensure that the streaming option is correctly set in the configuration files.

Run the following script to verify the streaming option:


132-137: Verify Pro-specific settings.

The method attempts to fetch configuration values from the options hash and falls back to the ReactOnRailsPro configuration if the react_on_rails_pro? condition is met. Ensure that the Pro-specific settings are accessible when applicable.

Run the following script to verify the Pro-specific settings:

Tools
rubocop

[convention] 134-134: Add empty line after guard clause.

(Layout/EmptyLineAfterGuardClause)

node_package/tests/ReactOnRails.test.js (1)

22-30: Verify impact on other test cases.

The test case has been modified to programmatically create the root div element and validate the text content of the root div instead of the component type. Ensure that the changes do not affect other test cases.

Run the following script to verify the impact on other test cases:

Verification successful

No impact on other test cases.

The changes in ReactOnRails.test.js are isolated to creating a div element and do not affect the test cases in Authenticity.test.js, which involve creating a meta tag for CSRF tokens. There is no shared state or dependency between these test cases.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the impact on other test cases.

# Test: Search for the test cases. Expect: No breaking changes.
rg --type js --type ts 'document.createElement'

Length of output: 220


Script:

#!/bin/bash
# Extract relevant lines from Authenticity.test.js to analyze the usage of document.createElement
rg --type js --type ts -A 5 'document.createElement' node_package/tests/Authenticity.test.js

Length of output: 261

node_package/src/types/index.ts (2)

2-2: LGTM!

The import statement for Readable from the stream module is correctly added.

The code changes are approved.


141-141: LGTM!

The method streamServerRenderedReactComponent is correctly added to the ReactOnRails interface.

The code changes are approved.

node_package/src/serverRenderReactComponent.ts (3)

170-175: LGTM!

The helper function stringToStream is correctly implemented to convert a string to a readable stream using PassThrough.

The code changes are approved.


177-265: LGTM!

The function streamServerRenderedReactComponent is correctly implemented to handle streaming server-rendered React components with proper error handling and logging mechanisms.

The code changes are approved.


267-267: LGTM!

The default export remains unchanged and there are no issues.

The code changes are approved.

lib/react_on_rails/server_rendering_pool/ruby_embedded_java_script.rb (3)

59-63: LGTM!

The conditional logic correctly switches between eval_streaming_js and eval_js based on the stream flag.

The code changes are approved.


79-83: LGTM!

The refactoring to encapsulate the parsing logic in a separate method parse_result_and_replay_console_messages improves code clarity and maintainability.

The code changes are approved.


229-247: LGTM!

The method parse_result_and_replay_console_messages is correctly implemented to handle result parsing and console message replaying.

The code changes are approved.

node_package/src/ReactOnRails.ts (2)

2-2: LGTM!

The import statement has been correctly updated to include streamServerRenderedReactComponent.

The code changes are approved.

Also applies to: 8-8


245-251: LGTM!

The new method streamServerRenderedReactComponent is correctly implemented and integrates well with the existing structure.

The code changes are approved.

spec/dummy/spec/helpers/react_on_rails_helper_spec.rb (2)

10-10: LGTM!

The inclusion of ActionView::Helpers::TagHelper is correctly done.

The code changes are approved.


350-373: LGTM!

The new method rails_context_if_not_already_rendered is correctly implemented and the tests ensure its correct behavior.

The code changes are approved.

lib/react_on_rails/helper.rb (4)

94-114: LGTM!

The new method stream_react_component is correctly implemented and integrates well with the existing structure.

The code changes are approved.


355-363: LGTM!

The new method internal_stream_react_component is correctly implemented and integrates well with the existing structure.

The code changes are approved.


396-420: LGTM!

The new method build_react_component_result_for_server_streamed_content is correctly implemented and integrates well with the existing structure.

The code changes are approved.


529-537: LGTM!

The new method raise_prerender_error is correctly implemented and integrates well with the existing structure.

The code changes are approved.

Comments failed to post (1)
node_package/src/buildConsoleReplay.ts (1)

14-21: Use Array.isArray instead of instanceof Array.

instanceof Array returns false for array-like objects and arrays from other execution contexts. Use Array.isArray instead for better compatibility and reliability.

Apply this diff to fix the issue:

-  if (!(console.history instanceof Array)) {
+  if (!Array.isArray(console.history)) {
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

export function consoleReplay(skipFirstNumberOfMessages: number = 0): string {
  // console.history is a global polyfill used in server rendering.
  // $FlowFixMe
  if (!Array.isArray(console.history)) {
    return '';
  }

  const lines = console.history.slice(skipFirstNumberOfMessages).map(msg => {
Tools
Biome

[error] 17-17: Use Array.isArray() instead of instanceof Array.

instanceof Array returns false for array-like objects and arrays from other execution contexts.
Unsafe fix: Use Array.isArray() instead.

(lint/suspicious/useIsArray)

Copy link
Member

@justin808 justin808 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alexeyr-ci @AbanoubGhadban merge if you guys are confident.

Should I do a release after?

@AbanoubGhadban AbanoubGhadban force-pushed the abanoubghadban/pro426-add-support-for-console-replay-while-streaming-component branch from bcfddd6 to 6ad16f1 Compare October 20, 2024 13:31
@AbanoubGhadban AbanoubGhadban force-pushed the abanoubghadban/pro426-add-support-for-console-replay-while-streaming-component branch 5 times, most recently from bd57e3d to 49547bb Compare October 29, 2024 11:26
Base automatically changed from abanoubghadban/pro426-add-support-for-console-replay-while-streaming-component to master October 29, 2024 11:54
@AbanoubGhadban AbanoubGhadban force-pushed the pro431-handle-erros-happen-during-streaming-components branch from 7ce5cb5 to ad9b93c Compare October 29, 2024 13:21
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Outside diff range and nitpick comments (3)
lib/react_on_rails/react_component/render_options.rb (1)

90-92: Consider renaming the method to better reflect its purpose.

The current method name raise_non_shell_server_rendering_errors implies that it raises errors, but it actually retrieves a configuration value that determines whether errors should be raised. Consider renaming it to follow Ruby's predicate method naming convention:

-def raise_non_shell_server_rendering_errors
+def raise_non_shell_server_rendering_errors?
   retrieve_react_on_rails_pro_config_value_for(:raise_non_shell_server_rendering_errors)
 end
lib/react_on_rails/helper.rb (2)

573-581: Add unit tests for raise_prerender_error method

Consider adding unit tests for the new raise_prerender_error method to ensure it raises the expected exceptions under various conditions. This will help maintain robustness and prevent future regressions.


583-588: Improve readability of complex condition in should_raise_streaming_prerender_error?

The conditional logic within should_raise_streaming_prerender_error? is complex and may benefit from added clarity. Consider adding inline comments or refactoring the condition to enhance readability and maintainability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 4829dcd and ad9b93c.

📒 Files selected for processing (3)
  • lib/react_on_rails/helper.rb (2 hunks)
  • lib/react_on_rails/react_component/render_options.rb (2 hunks)
  • node_package/src/serverRenderReactComponent.ts (5 hunks)
🔇 Additional comments (2)
lib/react_on_rails/react_component/render_options.rb (1)

90-92: Verify integration with streaming error handling.

The new configuration retrieval methods are part of the error handling improvements for streaming components. Let's verify their usage in the streaming context:

Also applies to: 136-142

✅ Verification successful

Based on the gathered information, I can now generate the final response:

Error handling configuration is correctly integrated with streaming components

The raise_non_shell_server_rendering_errors configuration is properly integrated in the error handling flow:

  • It's used in helper.rb to control error handling during streaming component rendering
  • The configuration is retrieved through render_options.rb and used to determine whether to raise errors when the shell is not ready
  • The error handling system supports both streaming and non-streaming scenarios with proper error class hierarchies (PrerenderError, JsonParseError)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check how these configuration values are used in streaming-related code

# Test 1: Find files that handle streaming and reference these configurations
echo "=== Files handling streaming and error configurations ==="
rg -l "stream.*raise_non_shell_server_rendering_errors"

# Test 2: Check for potential error handling patterns in streaming code
echo "=== Streaming error handling patterns ==="
rg "rescue.*stream" -A 5

Length of output: 302


Script:

#!/bin/bash
# Let's broaden our search to understand the error handling context better

# Find all files referencing raise_non_shell_server_rendering_errors
echo "=== Files using raise_non_shell_server_rendering_errors ==="
rg "raise_non_shell_server_rendering_errors" -l

# Check for streaming-related code
echo "=== Files with streaming functionality ==="
rg "stream" --type ruby -l

# Look for error handling in component rendering
echo "=== Error handling in component rendering ==="
rg "rescue" --type ruby -A 5 lib/react_on_rails/

# Check the configuration usage
echo "=== Configuration usage ==="
rg "ReactOnRails\.configuration" --type ruby -A 3

Length of output: 29632


Script:

#!/bin/bash
# Let's check how the error handling configuration is used in the codebase

# Check for error handling in helper.rb where most rendering logic resides
echo "=== Error handling in helper.rb ==="
rg "raise_non_shell_server_rendering_errors" lib/react_on_rails/helper.rb -A 5

# Check for error handling in render_options.rb
echo "=== Error handling in render_options.rb ==="
rg "raise_non_shell_server_rendering_errors" lib/react_on_rails/react_component/render_options.rb -A 5

# Check for server rendering error handling
echo "=== Server rendering error handling ==="
rg "PrerenderError|ReactOnRails::Error" --type ruby -A 5

Length of output: 25888

lib/react_on_rails/helper.rb (1)

641-641: Previous comment still applies: Correct the grammar in the inline comment

The inline comment on line 641 has grammatical errors that were previously pointed out. Please update the comment for clarity.

Comment on lines +136 to +142
def retrieve_react_on_rails_pro_config_value_for(key)
options.fetch(key) do
return nil unless ReactOnRails::Utils.react_on_rails_pro?

ReactOnRailsPro.configuration.public_send(key)
end
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance error handling and add documentation for Pro configuration retrieval.

The method silently returns nil for non-Pro installations and might not handle missing configuration keys in ReactOnRailsPro gracefully. Consider:

  1. Adding documentation to clarify the behavior
  2. Adding error handling for missing Pro configuration keys
  3. Consider logging a warning when accessed in non-Pro installations
+  # Retrieves a configuration value from ReactOnRailsPro if available
+  # @param key [Symbol] the configuration key to retrieve
+  # @return [Object, nil] the configuration value or nil if Pro is not enabled
+  # @raise [ReactOnRails::Error] if the configuration key doesn't exist in Pro config
   def retrieve_react_on_rails_pro_config_value_for(key)
     options.fetch(key) do
-      return nil unless ReactOnRails::Utils.react_on_rails_pro?
+      unless ReactOnRails::Utils.react_on_rails_pro?
+        Rails.logger.debug("Attempted to access Pro configuration '#{key}' in non-Pro installation")
+        return nil
+      end
 
-      ReactOnRailsPro.configuration.public_send(key)
+      unless ReactOnRailsPro.configuration.respond_to?(key)
+        raise ReactOnRails::Error, "Missing configuration key '#{key}' in ReactOnRailsPro"
+      end
+
+      ReactOnRailsPro.configuration.public_send(key)
     end
   end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def retrieve_react_on_rails_pro_config_value_for(key)
options.fetch(key) do
return nil unless ReactOnRails::Utils.react_on_rails_pro?
ReactOnRailsPro.configuration.public_send(key)
end
end
# Retrieves a configuration value from ReactOnRailsPro if available
# @param key [Symbol] the configuration key to retrieve
# @return [Object, nil] the configuration value or nil if Pro is not enabled
# @raise [ReactOnRails::Error] if the configuration key doesn't exist in Pro config
def retrieve_react_on_rails_pro_config_value_for(key)
options.fetch(key) do
unless ReactOnRails::Utils.react_on_rails_pro?
Rails.logger.debug("Attempted to access Pro configuration '#{key}' in non-Pro installation")
return nil
end
unless ReactOnRailsPro.configuration.respond_to?(key)
raise ReactOnRails::Error, "Missing configuration key '#{key}' in ReactOnRailsPro"
end
ReactOnRailsPro.configuration.public_send(key)
end
end

Comment on lines 228 to 229
consoleHistory = console.history;
const transformStream = new PassThrough({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure 'console.history' is available

console.history may not be defined in all environments. To prevent potential runtime errors, consider providing a default value when it's undefined.

Apply this diff:

-        consoleHistory = console.history;
+        consoleHistory = console.history || [];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
consoleHistory = console.history;
const transformStream = new PassThrough({
consoleHistory = console.history || [];
const transformStream = new PassThrough({

Comment on lines +198 to +199
stream.write(str);
stream.end();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor 'stringToStream' using 'Readable.from'

You can simplify the stringToStream function by utilizing Readable.from, which is available in Node.js v12 and above. This approach makes the code more concise and leverages built-in stream utilities.

Apply this diff:

-const stringToStream = (str: string): Readable => {
-  const stream = new PassThrough();
-  stream.write(str);
-  stream.end();
-  return stream;
-};
+const stringToStream = (str: string): Readable => Readable.from([str]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
stream.write(str);
stream.end();
const stringToStream = (str: string): Readable => Readable.from([str]);

Comment on lines 264 to 268
onError() {
// Can't through error here if throwJsErrors is true because the error will happen inside the stream
// And will not be handled by any catch clause
hasErrors = true;
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle errors in 'onError' callback consistently

In the onError callback, the error is currently not being handled or communicated to the client. To ensure consistent error handling, consider processing the error similarly to onShellError.

Apply this diff:

-          onError() {
+          onError(e) {
             // Can't throw an error here if throwJsErrors is true because the error will happen inside the stream
             // and will not be handled by any catch clause
             hasErrors = true;
+            const error = e instanceof Error ? e : new Error(String(e));
+            transformStream.write(handleError({
+              e: error,
+              name: componentName,
+              serverSide: true,
+            }));
+            transformStream.end();
          },

This ensures that errors encountered during rendering are properly relayed to the client.

Committable suggestion was skipped due to low confidence.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Outside diff range and nitpick comments (3)
node_package/src/types/index.ts (1)

141-141: Add JSDoc comment to document the isShellReady property.

The addition of isShellReady looks good and aligns with the PR's objective of improving streaming component handling. However, consider adding documentation to explain its purpose and usage.

 export interface RenderResult {
   html: string | null;
   consoleReplayScript: string;
   hasErrors: boolean;
   renderingError?: RenderingError;
+  /** Indicates whether the shell component is ready during streaming render */
   isShellReady?: boolean;
 }
node_package/tests/streamServerRenderedReactComponent.test.jsx (2)

43-51: Add error handling for JSON parsing in expectStreamChunk

When parsing chunk as JSON, it's good practice to handle potential parsing errors to prevent unhandled exceptions during tests.

Apply this diff to add error handling:

 const expectStreamChunk = (chunk) => {
   expect(typeof chunk).toBe('string');
-  const jsonChunk = JSON.parse(chunk);
+  let jsonChunk;
+  try {
+    jsonChunk = JSON.parse(chunk);
+  } catch (error) {
+    throw new Error(`Failed to parse chunk as JSON: ${error.message}`);
+  }
   expect(typeof jsonChunk.html).toBe('string');
   expect(typeof jsonChunk.consoleReplayScript).toBe('string');
   expect(typeof jsonChunk.hasErrors).toBe('boolean');
   expect(typeof jsonChunk.isShellReady).toBe('boolean');
   return jsonChunk;
 };

99-99: Simplify regular expressions in .toMatch assertions

The regular expressions used in .toMatch assertions can be simplified for clarity. Using [\s\S]* effectively matches any character including line breaks.

Apply this diff to simplify the regular expressions:

-expect(chunks[0].html).toMatch(/<pre>Exception in rendering[.\s\S]*Sync Error[.\s\S]*<\/pre>/);
+expect(chunks[0].html).toMatch(/<pre>Exception in rendering[\s\S]*Sync Error[\s\S]*<\/pre>/);

...

-expect(chunks[1].html).toMatch(/<script>[.\s\S]*Async Error[.\s\S]*<\/script>/);
+expect(chunks[1].html).toMatch(/<script>[\s\S]*Async Error[\s\S]*<\/script>/);

Also applies to: 113-113, 132-132, 151-151

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ad9b93c and b2af578.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (4)
  • node_package/src/serverRenderReactComponent.ts (5 hunks)
  • node_package/src/types/index.ts (1 hunks)
  • node_package/tests/streamServerRenderedReactComponent.test.jsx (1 hunks)
  • package.json (1 hunks)
🔇 Additional comments (9)
package.json (1)

42-43: Caution: Using React canary build in production

Switching to a specific canary build (18.3.0-canary-670811593-20240322) raises stability concerns:

  • Canary builds are experimental and not recommended for production use
  • They may contain breaking changes or unstable features
  • Consider using the latest stable version (18.2.0) unless there's a specific streaming-related feature in this canary that's essential for the error handling improvements

Let's verify if this specific canary version is required for the streaming error handling improvements:

✅ Verification successful

Let me search for more specific information about streaming error handling improvements in the codebase.


Let me try a different search approach focusing on the test files and error handling code.


React canary version required for improved streaming error handling

Based on the test files, the canary version is justified as it adds enhanced error handling capabilities for streaming components:

  • Support for both synchronous and asynchronous error handling in streamed components
  • Granular control over error propagation with throwJsErrors flag
  • Proper error state management in streaming chunks with hasErrors and isShellReady flags

The specific canary version (18.3.0-canary-670811593-20240322) appears to be required for these streaming error handling improvements, making it a necessary upgrade despite being a pre-release version.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for streaming-related changes that might depend on React 18.3 features
rg -g '!package*.json' -i "18.3|canary" 

# Look for streaming-related comments or documentation
rg -g '!package*.json' -i "streaming.*error|error.*streaming"

Length of output: 38961


Script:

#!/bin/bash
# Look for streaming error handling related code and changes
rg -A 5 -B 5 "streaming.*error|error.*streaming|throwSyncError|throwAsyncError" --type js --type jsx --type ts --type tsx

# Look for any documentation about streaming error handling
fd -e md -e mdx | xargs rg -l "streaming.*error|error.*streaming"

# Check for any test files related to streaming errors
fd -e test.js -e test.jsx -e test.ts -e test.tsx | xargs rg -l "streaming.*error|error.*streaming"

Length of output: 320


Script:

#!/bin/bash
# Search for streaming and error related test files
rg -A 5 -B 5 "throwSyncError|throwAsyncError|streaming.*error" node_package/tests/

# Look for any documentation about streaming error handling in docs
rg -i "streaming.*error|error.*streaming" docs/

# Check the commit message or PR description for context
git log -p -1

Length of output: 14041

node_package/src/types/index.ts (1)

141-141: Verify integration with streaming render components.

Let's verify that this property is properly integrated with the streaming render components.

✅ Verification successful

Let me try a different search strategy to verify the integration.


Property isShellReady is properly integrated with streaming render components

The verification shows that isShellReady is well integrated within the streaming render system:

  • Defined in StreamRenderState type in serverRenderReactComponent.ts
  • Used in error handling logic for streaming renders
  • Properly tested in streaming render component tests with both success and error scenarios
  • Correctly initialized as false and set to true in onShellReady callback
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the usage of isShellReady in streaming-related files

# Test 1: Find streaming-related files that use isShellReady
echo "=== Files using isShellReady ==="
rg "isShellReady" --type ts --type tsx

# Test 2: Check StreamRenderState type definition
echo -e "\n=== StreamRenderState definition ==="
ast-grep --pattern 'type StreamRenderState = {
  $$$
  isShellReady: $_
  $$$
}'

Length of output: 313


Script:

#!/bin/bash
# Search for files containing isShellReady with proper context
echo "=== Files using isShellReady ==="
rg "isShellReady" -A 3 -B 3

# Search for any streaming render related types or components
echo -e "\n=== Streaming render related files ==="
rg "StreamRender|StreamingRender" -l

# Look for type definitions that might include isShellReady
echo -e "\n=== Type definitions with shell ready state ==="
ast-grep --pattern 'type $_ = {
  $$$
  isShellReady?: boolean
  $$$
}'

Length of output: 9844

node_package/tests/streamServerRenderedReactComponent.test.jsx (2)

40-41: Ensure proper cleanup of component registrations between tests

While ComponentRegistry.components().clear() is called in beforeEach, it's important to verify that components are correctly registered and unregistered to prevent side effects between tests.

Also applies to: 58-59


10-16: 🛠️ Refactor suggestion

Verify the use of async function components in React

Defining AsyncContent as an async function component may cause unexpected behavior, as React components are generally expected to be synchronous functions. While React 18 introduces support for asynchronous rendering and server-side Suspense, using async functions as components is not recommended. Consider refactoring AsyncContent to be a regular function or use a higher-order component to handle the asynchronous behavior.

Apply this diff to refactor AsyncContent:

-const AsyncContent = async ({ throwAsyncError }) => {
-  await new Promise((resolve) => setTimeout(resolve, 0));
-  if (throwAsyncError) {
-    throw new Error('Async Error');
-  }
-  return <div>Async Content</div>;
-};
+const AsyncContent = ({ throwAsyncError }) => {
+  if (throwAsyncError) {
+    throw new Error('Async Error');
+  }
+  return <div>Async Content</div>;
+};

If asynchronous data fetching is necessary, consider using a data-fetching library compatible with server-side Suspense.

node_package/src/serverRenderReactComponent.ts (5)

1-2: LGTM!

The import statements are correct, and using type in the import enhances clarity for TypeScript types.


18-21: LGTM!

The StreamRenderState type is appropriately defined, extending RenderState and including the new properties.


109-109: LGTM!

Including isShellReady in the result object conditionally is a sensible approach.


204-205: Duplicate comment regarding 'stringToStream' function


210-210: Duplicate comment: Ensure 'console.history' is available

Comment on lines +42 to +43
"react": "18.3.0-canary-670811593-20240322",
"react-dom": "18.3.0-canary-670811593-20240322",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Update @types/react to match React version*

There's a version mismatch between React and its type definitions:

  • React/React-DOM: 18.3.0-canary-670811593-20240322
  • @types/react & @types/react-dom: ^18.2.0

This could lead to type checking issues as the type definitions might not cover new features or changes in the canary build.

Apply this diff to align the versions:

-    "@types/react": "^18.2.0",
-    "@types/react-dom": "^18.2.0",
+    "@types/react": "^18.3.0",
+    "@types/react-dom": "^18.3.0",

Committable suggestion was skipped due to low confidence.

Comment on lines +69 to +70
const decodedText = new TextDecoder().decode(chunk);
chunks.push(expectStreamChunk(decodedText));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Simplify chunk decoding in the stream handler

Using TextDecoder to decode chunks may not be necessary if the stream is set to emit strings. You can set the stream's encoding to 'utf8' to receive chunks as strings directly, simplifying the code.

Apply this diff to simplify chunk handling:

-renderResult.on('data', (chunk) => {
-  const decodedText = new TextDecoder().decode(chunk);
-  chunks.push(expectStreamChunk(decodedText));
-});
+renderResult.setEncoding('utf8');
+renderResult.on('data', (chunk) => {
+  chunks.push(expectStreamChunk(chunk));
+});

Committable suggestion was skipped due to low confidence.

Comment on lines +76 to +156
it('streamServerRenderedReactComponent streams the rendered component', async () => {
const { renderResult, chunks } = setupStreamTest();
await new Promise((resolve) => renderResult.on('end', resolve));

expect(chunks).toHaveLength(2);
expect(chunks[0].html).toContain('Header In The Shell');
expect(chunks[0].consoleReplayScript).toBe('');
expect(chunks[0].hasErrors).toBe(false);
expect(chunks[0].isShellReady).toBe(true);
expect(chunks[1].html).toContain('Async Content');
expect(chunks[1].consoleReplayScript).toBe('');
expect(chunks[1].hasErrors).toBe(false);
expect(chunks[1].isShellReady).toBe(true);
});

it('emits an error if there is an error in the shell and throwJsErrors is true', async () => {
const { renderResult, chunks } = setupStreamTest({ throwSyncError: true, throwJsErrors: true });
const onError = jest.fn();
renderResult.on('error', onError);
await new Promise((resolve) => renderResult.on('end', resolve));

expect(onError).toHaveBeenCalled();
expect(chunks).toHaveLength(1);
expect(chunks[0].html).toMatch(/<pre>Exception in rendering[.\s\S]*Sync Error[.\s\S]*<\/pre>/);
expect(chunks[0].consoleReplayScript).toBe('');
expect(chunks[0].hasErrors).toBe(true);
expect(chunks[0].isShellReady).toBe(false);
});

it("doesn't emit an error if there is an error in the shell and throwJsErrors is false", async () => {
const { renderResult, chunks } = setupStreamTest({ throwSyncError: true, throwJsErrors: false });
const onError = jest.fn();
renderResult.on('error', onError);
await new Promise((resolve) => renderResult.on('end', resolve));

expect(onError).not.toHaveBeenCalled();
expect(chunks).toHaveLength(1);
expect(chunks[0].html).toMatch(/<pre>Exception in rendering[.\s\S]*Sync Error[.\s\S]*<\/pre>/);
expect(chunks[0].consoleReplayScript).toBe('');
expect(chunks[0].hasErrors).toBe(true);
expect(chunks[0].isShellReady).toBe(false);
});

it('emits an error if there is an error in the async content and throwJsErrors is true', async () => {
const { renderResult, chunks } = setupStreamTest({ throwAsyncError: true, throwJsErrors: true });
const onError = jest.fn();
renderResult.on('error', onError);
await new Promise((resolve) => renderResult.on('end', resolve));

expect(onError).toHaveBeenCalled();
expect(chunks).toHaveLength(2);
expect(chunks[0].html).toContain('Header In The Shell');
expect(chunks[0].consoleReplayScript).toBe('');
expect(chunks[0].hasErrors).toBe(false);
expect(chunks[0].isShellReady).toBe(true);
// Script that fallbacks the render to client side
expect(chunks[1].html).toMatch(/<script>[.\s\S]*Async Error[.\s\S]*<\/script>/);
expect(chunks[1].consoleReplayScript).toBe('');
expect(chunks[1].hasErrors).toBe(true);
expect(chunks[1].isShellReady).toBe(true);
});

it("doesn't emit an error if there is an error in the async content and throwJsErrors is false", async () => {
const { renderResult, chunks } = setupStreamTest({ throwAsyncError: true, throwJsErrors: false });
const onError = jest.fn();
renderResult.on('error', onError);
await new Promise((resolve) => renderResult.on('end', resolve));

expect(onError).not.toHaveBeenCalled();
expect(chunks).toHaveLength(2);
expect(chunks[0].html).toContain('Header In The Shell');
expect(chunks[0].consoleReplayScript).toBe('');
expect(chunks[0].hasErrors).toBe(false);
expect(chunks[0].isShellReady).toBe(true);
// Script that fallbacks the render to client side
expect(chunks[1].html).toMatch(/<script>[.\s\S]*Async Error[.\s\S]*<\/script>/);
expect(chunks[1].consoleReplayScript).toBe('');
expect(chunks[1].hasErrors).toBe(true);
expect(chunks[1].isShellReady).toBe(true);
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor tests to reduce duplicated code

Multiple tests have similar setup and assertion logic, leading to code duplication. Consider refactoring common patterns into helper functions to improve maintainability.

For example, create a helper to wait for the stream to end:

+const waitForStreamEnd = (renderResult) =>
+  new Promise((resolve) => renderResult.on('end', resolve));

 // Usage in tests:
-await new Promise((resolve) => renderResult.on('end', resolve));
+await waitForStreamEnd(renderResult);

Additionally, you can abstract repeated assertions:

+const expectChunk = (chunk, expectedValues) => {
+  expect(chunk.html).toMatch(expectedValues.html);
+  expect(chunk.consoleReplayScript).toBe(expectedValues.consoleReplayScript);
+  expect(chunk.hasErrors).toBe(expectedValues.hasErrors);
+  expect(chunk.isShellReady).toBe(expectedValues.isShellReady);
+};

 // Usage in tests:
-expect(chunks[0].html).toContain('Header In The Shell');
-expect(chunks[0].consoleReplayScript).toBe('');
-expect(chunks[0].hasErrors).toBe(false);
-expect(chunks[0].isShellReady).toBe(true);
+expectChunk(chunks[0], {
+  html: expect.stringContaining('Header In The Shell'),
+  consoleReplayScript: '',
+  hasErrors: false,
+  isShellReady: true,
+});

Committable suggestion was skipped due to low confidence.

ReactDOMServer.renderToPipeableStream(reactRenderingResult).pipe(transformStream);

renderResult = transformStream;
return streamRenderReactComponen(reactRenderingResult, options);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Update function call to match corrected function name

After correcting the function name, update the function call accordingly to maintain consistency.

Apply this diff:

-        return streamRenderReactComponen(reactRenderingResult, options);
+        return streamRenderReactComponent(reactRenderingResult, options);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return streamRenderReactComponen(reactRenderingResult, options);
return streamRenderReactComponent(reactRenderingResult, options);

node_package/src/serverRenderReactComponent.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (1)
lib/react_on_rails/helper.rb (1)

641-641: Fix grammatical error in comment.

The comment has a grammatical error.

-          # It doesn't make any transformation, it listens and raises error if a chunk has errors
+          # It doesn't make any transformation, it listens and raises an error if a chunk has errors
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b2af578 and 63cc100.

📒 Files selected for processing (1)
  • lib/react_on_rails/helper.rb (2 hunks)
🔇 Additional comments (1)
lib/react_on_rails/helper.rb (1)

573-581: LGTM! Well-structured error handling.

The method effectively centralizes error handling logic and includes all necessary debugging information (sanitized props, console messages) in the error object.

Comment on lines 583 to 587
def should_raise_streaming_prerender_error?(chunk_json_result, render_options)
chunk_json_result["hasErrors"] &&
((render_options.raise_on_prerender_error && !chunk_json_result["isShellReady"]) ||
(render_options.raise_non_shell_server_rendering_errors && chunk_json_result["isShellReady"]))
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider improving readability of error condition checks.

The boolean logic could be more readable by extracting conditions into well-named variables.

Consider this refactor:

-    def should_raise_streaming_prerender_error?(chunk_json_result, render_options)
-      chunk_json_result["hasErrors"] &&
-        ((render_options.raise_on_prerender_error && !chunk_json_result["isShellReady"]) ||
-         (render_options.raise_non_shell_server_rendering_errors && chunk_json_result["isShellReady"]))
-    end
+    def should_raise_streaming_prerender_error?(chunk_json_result, render_options)
+      has_errors = chunk_json_result["hasErrors"]
+      is_shell_ready = chunk_json_result["isShellReady"]
+      
+      raise_non_shell_error = render_options.raise_on_prerender_error && !is_shell_ready
+      raise_shell_error = render_options.raise_non_shell_server_rendering_errors && is_shell_ready
+      
+      has_errors && (raise_non_shell_error || raise_shell_error)
+    end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def should_raise_streaming_prerender_error?(chunk_json_result, render_options)
chunk_json_result["hasErrors"] &&
((render_options.raise_on_prerender_error && !chunk_json_result["isShellReady"]) ||
(render_options.raise_non_shell_server_rendering_errors && chunk_json_result["isShellReady"]))
end
def should_raise_streaming_prerender_error?(chunk_json_result, render_options)
has_errors = chunk_json_result["hasErrors"]
is_shell_ready = chunk_json_result["isShellReady"]
raise_non_shell_error = render_options.raise_on_prerender_error && !is_shell_ready
raise_shell_error = render_options.raise_non_shell_server_rendering_errors && is_shell_ready
has_errors && (raise_non_shell_error || raise_shell_error)
end

lib/react_on_rails/helper.rb Outdated Show resolved Hide resolved
node_package/src/serverRenderReactComponent.ts Outdated Show resolved Hide resolved
node_package/src/serverRenderReactComponent.ts Outdated Show resolved Hide resolved
node_package/src/serverRenderReactComponent.ts Outdated Show resolved Hide resolved
AbanoubGhadban and others added 2 commits October 31, 2024 16:58
Co-authored-by: Alexey Romanov <[email protected]>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 63cc100 and e7e3857.

📒 Files selected for processing (1)
  • lib/react_on_rails/helper.rb (2 hunks)
🧰 Additional context used
🪛 rubocop
lib/react_on_rails/helper.rb

[convention] 585-585: Line is too long. [142/120]

(Layout/LineLength)

🔇 Additional comments (1)
lib/react_on_rails/helper.rb (1)

573-581: Well-structured error handling implementation!

The method effectively encapsulates error raising logic with proper sanitization of props and inclusion of console messages for debugging.

Comment on lines +635 to +643
if render_options.stream?
result.transform do |chunk_json_result|
if should_raise_streaming_prerender_error?(chunk_json_result, render_options)
raise_prerender_error(chunk_json_result, react_component_name, props, js_code)
end
# It doesn't make any transformation, it listens and raises error if a chunk has errors
chunk_json_result
end
elsif result["hasErrors"] && render_options.raise_on_prerender_error
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve clarity of streaming error handling.

The current implementation uses a transform block for error checking, which might be misleading since no actual transformation occurs.

Consider this refactor to make the intent clearer:

       if render_options.stream?
-        result.transform do |chunk_json_result|
-          if should_raise_streaming_prerender_error?(chunk_json_result, render_options)
-            raise_prerender_error(chunk_json_result, react_component_name, props, js_code)
-          end
-          # It doesn't make any transformation, it listens and raises error if a chunk has errors
-          chunk_json_result
-        end
+        result.each_chunk do |chunk_json_result|
+          validate_streaming_chunk(
+            chunk_json_result,
+            render_options,
+            react_component_name,
+            props,
+            js_code
+          )
+          chunk_json_result
+        end

Add this private method:

def validate_streaming_chunk(chunk_json_result, render_options, react_component_name, props, js_code)
  if should_raise_streaming_prerender_error?(chunk_json_result, render_options)
    raise_prerender_error(chunk_json_result, react_component_name, props, js_code)
  end
end

This refactor:

  1. Uses a more appropriate method name (each_chunk instead of transform)
  2. Extracts validation logic into a dedicated method
  3. Makes the error checking purpose more explicit

Comment on lines 583 to 586
def should_raise_streaming_prerender_error?(chunk_json_result, render_options)
chunk_json_result["hasErrors"] &&
(chunk_json_result["isShellReady"] ? render_options.raise_non_shell_server_rendering_errors : render_options.raise_on_prerender_error)
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve readability of error condition check.

The current implementation, while functional, could be more readable by breaking down the complex conditional logic.

Consider this refactor for better readability:

-    def should_raise_streaming_prerender_error?(chunk_json_result, render_options)
-      chunk_json_result["hasErrors"] &&
-        (chunk_json_result["isShellReady"] ? render_options.raise_non_shell_server_rendering_errors : render_options.raise_on_prerender_error)
-    end
+    def should_raise_streaming_prerender_error?(chunk_json_result, render_options)
+      return false unless chunk_json_result["hasErrors"]
+      
+      if chunk_json_result["isShellReady"]
+        render_options.raise_non_shell_server_rendering_errors
+      else
+        render_options.raise_on_prerender_error
+      end
+    end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def should_raise_streaming_prerender_error?(chunk_json_result, render_options)
chunk_json_result["hasErrors"] &&
(chunk_json_result["isShellReady"] ? render_options.raise_non_shell_server_rendering_errors : render_options.raise_on_prerender_error)
end
def should_raise_streaming_prerender_error?(chunk_json_result, render_options)
return false unless chunk_json_result["hasErrors"]
if chunk_json_result["isShellReady"]
render_options.raise_non_shell_server_rendering_errors
else
render_options.raise_on_prerender_error
end
end
🧰 Tools
🪛 rubocop

[convention] 585-585: Line is too long. [142/120]

(Layout/LineLength)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between e7e3857 and 3b7728a.

📒 Files selected for processing (1)
  • node_package/src/serverRenderReactComponent.ts (5 hunks)
🧰 Additional context used
🪛 eslint
node_package/src/serverRenderReactComponent.ts

[error] 247-247: 'streamRenderReactComponent' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

🪛 GitHub Check: examples (newest)
node_package/src/serverRenderReactComponent.ts

[failure] 316-316:
Cannot find name 'streamRenderReactComponen'. Did you mean 'streamRenderReactComponent'?

🪛 GitHub Check: build-dummy-app-webpack-test-bundles (newest)
node_package/src/serverRenderReactComponent.ts

[failure] 316-316:
Cannot find name 'streamRenderReactComponen'. Did you mean 'streamRenderReactComponent'?

🪛 GitHub Check: examples (oldest)
node_package/src/serverRenderReactComponent.ts

[failure] 316-316:
Cannot find name 'streamRenderReactComponen'. Did you mean 'streamRenderReactComponent'?

🔇 Additional comments (4)
node_package/src/serverRenderReactComponent.ts (4)

1-1: LGTM! Well-structured type definitions.

The new StreamRenderState type properly extends RenderState with streaming-specific properties, maintaining type safety.

Also applies to: 18-21


103-110: LGTM! Robust type handling in createResultObject.

The function elegantly handles both regular and streaming render states using proper type narrowing.


209-245: LGTM! Well-implemented stream transformation.

The implementation correctly:

  • Manages console history state across chunks
  • Properly transforms chunks into JSON results
  • Handles error propagation through stream wrapping
  • Provides clear control methods for stream management

316-316: ⚠️ Potential issue

Fix typo in function name.

The function name has a typo: 'streamRenderReactComponen' should be 'streamRenderReactComponent'.

-    return streamRenderReactComponen(reactRenderingResult, options);
+    return streamRenderReactComponent(reactRenderingResult, options);

Likely invalid or redundant comment.

🧰 Tools
🪛 GitHub Check: examples (newest)

[failure] 316-316:
Cannot find name 'streamRenderReactComponen'. Did you mean 'streamRenderReactComponent'?

🪛 GitHub Check: build-dummy-app-webpack-test-bundles (newest)

[failure] 316-316:
Cannot find name 'streamRenderReactComponen'. Did you mean 'streamRenderReactComponent'?

🪛 GitHub Check: examples (oldest)

[failure] 316-316:
Cannot find name 'streamRenderReactComponen'. Did you mean 'streamRenderReactComponent'?

Comment on lines 265 to 266
const error = e instanceof Error ? e : new Error(String(e));
renderState.hasErrors = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider extracting error conversion logic.

The error conversion logic is duplicated. Consider extracting it into a helper function for better maintainability.

+const convertToError = (e: unknown): Error => 
+  e instanceof Error ? e : new Error(String(e));

-const error = e instanceof Error ? e : new Error(String(e));
+const error = convertToError(e);

Also applies to: 285-286

return { readableStream: readableStream as Readable, pipeToTransform, writeChunk, emitError, endStream };
}

const streamRenderReactComponent = (reactRenderingResult: ReactElement, options: RenderParams) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Export streamRenderReactComponent function.

The function is used internally but not exported, causing an unused variable warning. Consider exporting it or marking it as internal.

-const streamRenderReactComponent = (reactRenderingResult: ReactElement, options: RenderParams) => {
+export const streamRenderReactComponent = (reactRenderingResult: ReactElement, options: RenderParams) => {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const streamRenderReactComponent = (reactRenderingResult: ReactElement, options: RenderParams) => {
export const streamRenderReactComponent = (reactRenderingResult: ReactElement, options: RenderParams) => {
🧰 Tools
🪛 eslint

[error] 247-247: 'streamRenderReactComponent' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (1)
CHANGELOG.md (1)

21-22: Fix list indentation and enhance the changelog entry

The changelog entry has incorrect indentation and could benefit from more details about the error handling capabilities.

  1. Fix the indentation by removing the extra space before the bullet point:
-  - Added support for handling errors happening during server rendering of streamed React components. It handles errors that happen during the initial render and errors that happen inside suspense boundaries. [PR #1648](https://github.com/shakacode/react_on_rails/pull/1648) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
+ - Added support for handling errors happening during server rendering of streamed React components. It handles errors that happen during the initial render and errors that happen inside suspense boundaries. [PR #1648](https://github.com/shakacode/react_on_rails/pull/1648) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
  1. Consider expanding the entry to include:
    • Examples of the types of errors that are handled
    • How errors are propagated/displayed to users
    • Any configuration options available
🧰 Tools
🪛 Markdownlint

22-22: Expected: 0; Actual: 1
Unordered list indentation

(MD007, ul-indent)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 3b7728a and f36d74d.

📒 Files selected for processing (3)
  • CHANGELOG.md (1 hunks)
  • lib/react_on_rails/helper.rb (2 hunks)
  • node_package/src/serverRenderReactComponent.ts (5 hunks)
🧰 Additional context used
🪛 Markdownlint
CHANGELOG.md

22-22: Expected: 0; Actual: 1
Unordered list indentation

(MD007, ul-indent)

🔇 Additional comments (8)
node_package/src/serverRenderReactComponent.ts (6)

18-21: LGTM: Well-structured type definition

The StreamRenderState type properly extends RenderState with streaming-specific fields, maintaining type safety while adding the necessary isShellReady flag for tracking the shell rendering state.


30-32: LGTM: Good error conversion helper

The convertToError helper function effectively addresses the code duplication concern raised in previous reviews while maintaining proper error handling.


99-103: LGTM: Consistent error handling

The error handling implementation properly utilizes the new convertToError helper while maintaining the expected error structure for client-side handling.


107-114: LGTM: Well-implemented result object creation

The function elegantly handles both streaming and non-streaming states while maintaining type safety through proper type narrowing for the isShellReady field.


320-320: LGTM: Proper streaming implementation with consistent error handling

The implementation correctly utilizes the new streaming functionality while maintaining consistent error handling patterns throughout the codebase.

Also applies to: 326-329


213-249: 🛠️ Refactor suggestion

Consider adding error handling for transform stream

While the stream transformation logic is well-implemented, consider adding error handling for the transform stream itself to ensure all potential failure points are covered.

Consider adding error handling:

 const transformStream = new PassThrough({
+  // Handle transform stream errors
+  error(err) {
+    console.error('Transform stream error:', err);
+    this.end();
+  },
   transform(chunk, _, callback) {
lib/react_on_rails/helper.rb (2)

573-581: LGTM! Well-structured error handling.

The raise_prerender_error method is well-implemented with clear separation of concerns and proper error information collection.


639-647: 🛠️ Refactor suggestion

Replace transform with each_chunk for clarity.

The current implementation uses transform but doesn't actually transform the data. Using each_chunk would better reflect the intent of the code.

       if render_options.stream?
-        result.transform do |chunk_json_result|
+        result.each_chunk do |chunk_json_result|
           if should_raise_streaming_prerender_error?(chunk_json_result, render_options)
             raise_prerender_error(chunk_json_result, react_component_name, props, js_code)
           end
-          # It doesn't make any transformation, it listens and raises error if a chunk has errors
           chunk_json_result
         end
       elsif result["hasErrors"] && render_options.raise_on_prerender_error

Likely invalid or redundant comment.

Comment on lines +285 to +295
onError(e) {
if (!renderState.isShellReady) {
return;
}
const error = convertToError(e);
if (throwJsErrors) {
emitError(error);
}
renderState.hasErrors = true;
renderState.error = error;
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Consider enhancing error handling in onError callback

The onError callback could be enhanced to handle errors more consistently with onShellError. Currently, it updates the state but doesn't write the error to the stream when throwJsErrors is false.

Consider updating the implementation:

 onError(e) {
   if (!renderState.isShellReady) {
     return;
   }
   const error = convertToError(e);
   if (throwJsErrors) {
     emitError(error);
   }
   renderState.hasErrors = true;
   renderState.error = error;
+  if (!throwJsErrors) {
+    const errorHtml = handleError({ e: error, name: componentName, serverSide: true });
+    writeChunk(errorHtml);
+  }
 },

Committable suggestion was skipped due to low confidence.

Comment on lines +583 to +590
def should_raise_streaming_prerender_error?(chunk_json_result, render_options)
chunk_json_result["hasErrors"] &&
(if chunk_json_result["isShellReady"]
render_options.raise_non_shell_server_rendering_errors
else
render_options.raise_on_prerender_error
end)
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Simplify the conditional logic for better readability.

The current implementation uses a nested if statement that could be simplified using the ternary operator.

-    def should_raise_streaming_prerender_error?(chunk_json_result, render_options)
-      chunk_json_result["hasErrors"] &&
-        (if chunk_json_result["isShellReady"]
-           render_options.raise_non_shell_server_rendering_errors
-         else
-           render_options.raise_on_prerender_error
-         end)
-    end
+    def should_raise_streaming_prerender_error?(chunk_json_result, render_options)
+      chunk_json_result["hasErrors"] &&
+        (chunk_json_result["isShellReady"] ? 
+          render_options.raise_non_shell_server_rendering_errors : 
+          render_options.raise_on_prerender_error)
+    end
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def should_raise_streaming_prerender_error?(chunk_json_result, render_options)
chunk_json_result["hasErrors"] &&
(if chunk_json_result["isShellReady"]
render_options.raise_non_shell_server_rendering_errors
else
render_options.raise_on_prerender_error
end)
end
def should_raise_streaming_prerender_error?(chunk_json_result, render_options)
chunk_json_result["hasErrors"] &&
(chunk_json_result["isShellReady"] ?
render_options.raise_non_shell_server_rendering_errors :
render_options.raise_on_prerender_error)
end

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between f36d74d and 902e581.

📒 Files selected for processing (1)
  • CHANGELOG.md (1 hunks)
🧰 Additional context used
🪛 Markdownlint
CHANGELOG.md

21-21: Punctuation: '.'
Trailing punctuation in heading

(MD026, no-trailing-punctuation)


21-21: null
Bare URL used

(MD034, no-bare-urls)

🔇 Additional comments (1)
CHANGELOG.md (1)

21-28: LGTM! Clear and well-structured changelog entries

The changelog entries effectively document the new features:

  1. Streaming server rendering support with new helper and function
  2. Console log replay support for debugging
  3. Error handling improvements for streaming components

The entries follow the changelog format and provide good traceability by linking to the relevant PRs.

🧰 Tools
🪛 Markdownlint

21-21: Punctuation: '.'
Trailing punctuation in heading

(MD026, no-trailing-punctuation)


21-21: null
Bare URL used

(MD034, no-bare-urls)

- Added support for replaying console logs that occur during server rendering of streamed React components. This enables debugging of server-side rendering issues by capturing and displaying console output on the client and on the server output. [PR #1647](https://github.com/shakacode/react_on_rails/pull/1647) by [AbanoubGhadban](https://github.com/AbanoubGhadban).

### Added
#### Added(https://github.com/AbanoubGhadban).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix formatting of the Added section header

The section header has incorrect formatting:

  1. Contains a bare URL which should be properly linked
  2. Has trailing punctuation which should be removed

Apply this diff to fix the formatting:

-#### Added(https://github.com/AbanoubGhadban).
+#### Added
+Changes by [@AbanoubGhadban](https://github.com/AbanoubGhadban)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#### Added(https://github.com/AbanoubGhadban).
#### Added
Changes by [@AbanoubGhadban](https://github.com/AbanoubGhadban)
🧰 Tools
🪛 Markdownlint

21-21: Punctuation: '.'
Trailing punctuation in heading

(MD026, no-trailing-punctuation)


21-21: null
Bare URL used

(MD034, no-bare-urls)

@AbanoubGhadban AbanoubGhadban merged commit a87c1e6 into master Oct 31, 2024
11 checks passed
@AbanoubGhadban AbanoubGhadban deleted the pro431-handle-erros-happen-during-streaming-components branch October 31, 2024 16:23
@coderabbitai coderabbitai bot mentioned this pull request Nov 11, 2024
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants